/*-------------------------------------------------------------------------
 *
 * resowner.c
 *	  POSTGRES 资源所有者管理代码。
 *
 * 查询生命周期资源通过将其与资源所有者对象关联进行跟踪。
 * 这提供了一种简单的机制，以确保这些资源在正确的时间得到释放。
 * 有关更多信息，请参见 utils/resowner/README。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/utils/resowner/resowner.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "common/cryptohash.h"
#include "common/hashfn.h"
#include "common/hmac.h"
#include "jit/jit.h"
#include "storage/bufmgr.h"
#include "storage/ipc.h"
#include "storage/predicate.h"
#include "storage/proc.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "utils/resowner_private.h"
#include "utils/snapmgr.h"


/*
 * 此代码管理的所有资源ID都必须适合Datum，
 * 这很好，因为它们通常是指针或整数。
 *
 * 提供Datum转换宏用于一些实际上就是“int”的东西。
 */
#define FileGetDatum(file) Int32GetDatum(file)
#define DatumGetFile(datum) ((File) DatumGetInt32(datum))
#define BufferGetDatum(buffer) Int32GetDatum(buffer)
#define DatumGetBuffer(datum) ((Buffer) DatumGetInt32(datum))

/*
 * ResourceArray是存储所有类型资源ID的通用结构。
 *
 * 我们通过将资源ID保存在简单数组中来管理小规模的资源ID集：
 * itemsarr[k]保存一个ID，满足0 <= k < nitems <= maxitems = 容量。
 *
 * 如果集合变得大，我们将切换到使用开放寻址哈希。
 * 然后，itemsarr[]是一个“容量”槽的哈希表，每个
 * 槽保存一个ID或“invalidval”。nitems是存在的有效
 * 项目的数量；如果超过maxitems，我们将扩大数组并
 * 重新哈希。在这种模式下，maxitems应远小于容量，以
 * 免于浪费太多时间搜索空槽。
 *
 * 在任何模式下，lastidx会记住最近插入的项目的位置
 * 或通过GetAny返回的项目；这加速了在ResourceArrayRemove中的搜索。
 */
typedef struct ResourceArray
{
	Datum	   *itemsarr;		/* 用于存储值的缓冲区 */
	Datum		invalidval;		/* 被认为无效的值 */
	uint32		capacity;		/* itemsarr[] 的分配长度 */
	uint32		nitems;			/* items 数组中存储的项目数量 */
	uint32		maxitems;		/* nitems 在增加之前的当前限制 */
	uint32		lastidx;		/* 由 GetAny 返回的最后一个项目的索引 */
} ResourceArray;

/*
 * ResourceArray 的初始分配大小。必须是二的幂，因为
 * 我们将使用 (arraysize - 1) 作为哈希的掩码。
 */
#define RESARRAY_INIT_SIZE 16

/*
 * 在 ResourceArray 中，什么时候切换到哈希 vs. 简单数组逻辑。
 */
#define RESARRAY_MAX_ARRAY 64
#define RESARRAY_IS_ARRAY(resarr) ((resarr)->capacity <= RESARRAY_MAX_ARRAY)

/*
 * 在给定容量的资源数组中可以存储多少个项目。
 * 当达到这个数量时，我们必须调整大小。
 */
#define RESARRAY_MAX_ITEMS(capacity) \
	((capacity) <= RESARRAY_MAX_ARRAY ? (capacity) : (capacity)/4 * 3)

/*
 * 为了加速从资源所有者向其父级批量释放或重新分配锁，
 * 每个资源所有者都有一个小的它拥有的锁的缓存。锁管理器在其
 * 本地锁哈希表中具有相同的信息，当缓存溢出时我们会退回到这一点，
 * 但当有很多锁属于其他资源所有者时，遍历哈希表会变得更慢。
 *
 * MAX_RESOWNER_LOCKS 是每个资源所有者缓存的大小。它是根据
 * 在大型架构的 pg_dump 测试中选择的。当在 9.2 版本上进行测试时，
 * pg_dump 运行中的资源所有者包含最多 9 个锁，无论架构大小，
 * 除了顶层资源所有者包含更多（溢出缓存）。15 似乎是一个不错的
 * 圆整数字，略高于 pg_dump 的需求。请注意，提高这个数字并不是
 * 免费的 - 缓存越大，释放锁的速度就越慢（在零售情况下），当
 * 一个资源所有者持有多个锁时。
 */
#define MAX_RESOWNER_LOCKS 15

/*
 * ResourceOwner 对象看起来是这样的
 */
typedef struct ResourceOwnerData
{
	ResourceOwner parent;		/* 如果没有父级则为 NULL（顶级所有者） */
	ResourceOwner firstchild;	/* 子节点的链表头 */
	ResourceOwner nextchild;	/* 同一父级的下一个子节点 */
	const char *name;			/* 名称（仅用于调试） */

	/* 我们内置支持记忆： */
	ResourceArray bufferarr;	/* 拥有的缓冲区 */
	ResourceArray catrefarr;	/* catcache 引用 */
	ResourceArray catlistrefarr;	/* catcache 列表引用 */
	ResourceArray relrefarr;	/* relcache 引用 */
	ResourceArray planrefarr;	/* plancache 引用 */
	ResourceArray tupdescarr;	/* tupdesc 引用 */
	ResourceArray snapshotarr;	/* 快照引用 */
	ResourceArray filearr;		/* 打开临时文件 */
	ResourceArray dsmarr;		/* 动态共享内存段 */
	ResourceArray jitarr;		/* JIT 上下文 */
	ResourceArray cryptohasharr;	/* cryptohash 上下文 */
	ResourceArray hmacarr;		/* HMAC 上下文 */

	/* 我们可以记住最多 MAX_RESOWNER_LOCKS 个本地锁的引用。 */
	int			nlocks;			/* 拥有的锁的数量 */
	LOCALLOCK  *locks[MAX_RESOWNER_LOCKS];	/* 拥有的锁列表 */
}			ResourceOwnerData;


/*****************************************************************************
 *	  GLOBAL MEMORY															 *
 *****************************************************************************/

ResourceOwner CurrentResourceOwner = NULL;
ResourceOwner CurTransactionResourceOwner = NULL;
ResourceOwner TopTransactionResourceOwner = NULL;
ResourceOwner AuxProcessResourceOwner = NULL;

/* 资源释放的附加回调列表 */
typedef struct ResourceReleaseCallbackItem
{
	struct ResourceReleaseCallbackItem *next;
	ResourceReleaseCallback callback;
	void	   *arg;
} ResourceReleaseCallbackItem;

static ResourceReleaseCallbackItem *ResourceRelease_callbacks = NULL;


/* 内部例程 */
static void fc_ResourceArrayInit(ResourceArray *fc_resarr, Datum fc_invalidval);
static void fc_ResourceArrayEnlarge(ResourceArray *fc_resarr);
static void fc_ResourceArrayAdd(ResourceArray *fc_resarr, Datum fc_value);
static bool fc_ResourceArrayRemove(ResourceArray *fc_resarr, Datum fc_value);
static bool fc_ResourceArrayGetAny(ResourceArray *fc_resarr, Datum *fc_value);
static void fc_ResourceArrayFree(ResourceArray *fc_resarr);
static void fc_ResourceOwnerReleaseInternal(ResourceOwner fc_owner,
										 ResourceReleasePhase fc_phase,
										 bool fc_isCommit,
										 bool fc_isTopLevel);
static void fc_ReleaseAuxProcessResourcesCallback(int fc_code, Datum fc_arg);
static void fc_PrintRelCacheLeakWarning(Relation fc_rel);
static void fc_PrintPlanCacheLeakWarning(CachedPlan *fc_plan);
static void fc_PrintTupleDescLeakWarning(TupleDesc fc_tupdesc);
static void fc_PrintSnapshotLeakWarning(Snapshot fc_snapshot);
static void fc_PrintFileLeakWarning(File fc_file);
static void fc_PrintDSMLeakWarning(dsm_segment *fc_seg);
static void fc_PrintCryptoHashLeakWarning(Datum fc_handle);
static void fc_PrintHMACLeakWarning(Datum fc_handle);


/*****************************************************************************
 *	  内部例程														 *
 *****************************************************************************/


/* 初始化一个 ResourceArray */
static void fc_ResourceArrayInit(ResourceArray *fc_resarr, Datum fc_invalidval)
{
	/* 断言它是空的 */
	Assert(fc_resarr->itemsarr == NULL);
	Assert(fc_resarr->capacity == 0);
	Assert(fc_resarr->nitems == 0);
	Assert(fc_resarr->maxitems == 0);
	/* 记住适当的“无效”值 */
	fc_resarr->invalidval = fc_invalidval;
	/* 我们不会在需要之前分配任何存储 */
}

/* 确保数组中至少还有一个资源的空间。 */
static void fc_ResourceArrayEnlarge(ResourceArray *fc_resarr)
{
	uint32		fc_i,
				fc_oldcap,
				fc_newcap;
	Datum	   *fc_olditemsarr;
	Datum	   *fc_newitemsarr;

	if (fc_resarr->nitems < fc_resarr->maxitems)
		return;					/* 不需要工作 */

	fc_olditemsarr = fc_resarr->itemsarr;
	fc_oldcap = fc_resarr->capacity;

	/* 将数组的容量加倍（容量必须保持为2的幂！） */
	fc_newcap = (fc_oldcap > 0) ? fc_oldcap * 2 : RESARRAY_INIT_SIZE;
	fc_newitemsarr = (Datum *) MemoryContextAlloc(TopMemoryContext,
											   fc_newcap * sizeof(Datum));
	for (fc_i = 0; fc_i < fc_newcap; fc_i++)
		fc_newitemsarr[fc_i] = fc_resarr->invalidval;

	/* 我们假设在这个点以下不会失败，因此可以在 resarr 上随意操作 */
	fc_resarr->itemsarr = fc_newitemsarr;
	fc_resarr->capacity = fc_newcap;
	fc_resarr->maxitems = RESARRAY_MAX_ITEMS(fc_newcap);
	fc_resarr->nitems = 0;

	if (fc_olditemsarr != NULL)
	{
		/* 将任何预存在条目转移到新的数组中；它们不一定会放在之前的位置，所以这种简单的逻辑是最好的方法。请注意，如果我们将集合管理为简单的数组，那么 nitems 之后的条目是无效的，但这不应影响，因为除非 nitems 等于 oldcap，否则我们不会到这里。 */
		for (fc_i = 0; fc_i < fc_oldcap; fc_i++)
		{
			if (fc_olditemsarr[fc_i] != fc_resarr->invalidval)
				fc_ResourceArrayAdd(fc_resarr, fc_olditemsarr[fc_i]);
		}

		/* 并释放旧数组。 */
		pfree(fc_olditemsarr);
	}

	Assert(fc_resarr->nitems < fc_resarr->maxitems);
}

/* 将资源添加到 ResourceArray */
static void fc_ResourceArrayAdd(ResourceArray *fc_resarr, Datum fc_value)
{
	uint32		fc_idx;

	Assert(fc_value != fc_resarr->invalidval);
	Assert(fc_resarr->nitems < fc_resarr->maxitems);

	if (RESARRAY_IS_ARRAY(fc_resarr))
	{
		/* 附加到线性数组。 */
		fc_idx = fc_resarr->nitems;
	}
	else
	{
		/* 在哈希位置后插入到第一个空槽中。 */
		uint32		fc_mask = fc_resarr->capacity - 1;

		fc_idx = DatumGetUInt32(hash_any((void *) &fc_value, sizeof(fc_value))) & fc_mask;
		for (;;)
		{
			if (fc_resarr->itemsarr[fc_idx] == fc_resarr->invalidval)
				break;
			fc_idx = (fc_idx + 1) & fc_mask;
		}
	}
	fc_resarr->lastidx = fc_idx;
	fc_resarr->itemsarr[fc_idx] = fc_value;
	fc_resarr->nitems++;
}

/* 从 ResourceArray 中移除资源 */
static bool fc_ResourceArrayRemove(ResourceArray *fc_resarr, Datum fc_value)
{
	uint32		fc_i,
				fc_idx,
				fc_lastidx = fc_resarr->lastidx;

	Assert(fc_value != fc_resarr->invalidval);

	/* 成功时返回 true，如果未找到资源则返回 false。 */
	if (RESARRAY_IS_ARRAY(fc_resarr))
	{
		if (fc_lastidx < fc_resarr->nitems &&
			fc_resarr->itemsarr[fc_lastidx] == fc_value)
		{
			fc_resarr->itemsarr[fc_lastidx] = fc_resarr->itemsarr[fc_resarr->nitems - 1];
			fc_resarr->nitems--;
			/* 注意：如果同一资源 ID 出现多次，则会移除一个实例。 */
			fc_resarr->lastidx = fc_resarr->nitems - 1;
			return true;
		}
		for (fc_i = 0; fc_i < fc_resarr->nitems; fc_i++)
		{
			if (fc_resarr->itemsarr[fc_i] == fc_value)
			{
				fc_resarr->itemsarr[fc_i] = fc_resarr->itemsarr[fc_resarr->nitems - 1];
				fc_resarr->nitems--;
				/* 注意：如果同一资源 ID 出现多次，则会移除一个实例。 */
				fc_resarr->lastidx = fc_resarr->nitems - 1;
				return true;
			}
		}
	}
	else
	{
		uint32		fc_mask = fc_resarr->capacity - 1;

		if (fc_lastidx < fc_resarr->capacity &&
			fc_resarr->itemsarr[fc_lastidx] == fc_value)
		{
			fc_resarr->itemsarr[fc_lastidx] = fc_resarr->invalidval;
			fc_resarr->nitems--;
			return true;
		}
		fc_idx = DatumGetUInt32(hash_any((void *) &fc_value, sizeof(fc_value))) & fc_mask;
		for (fc_i = 0; fc_i < fc_resarr->capacity; fc_i++)
		{
			if (fc_resarr->itemsarr[fc_idx] == fc_value)
			{
				fc_resarr->itemsarr[fc_idx] = fc_resarr->invalidval;
				fc_resarr->nitems--;
				return true;
			}
			fc_idx = (fc_idx + 1) & fc_mask;
		}
	}

	return false;
}

/* 搜索所有条目，但首先尝试 lastidx。 */
static bool fc_ResourceArrayGetAny(ResourceArray *fc_resarr, Datum *fc_value)
{
	if (fc_resarr->nitems == 0)
		return false;

	if (RESARRAY_IS_ARRAY(fc_resarr))
	{
		/* 更新 lastidx，以加快反向顺序删除。 */
		fc_resarr->lastidx = 0;
	}
	else
	{
		/* 获取 ResourceArray 中的任何便利条目。 */
		uint32		fc_mask = fc_resarr->capacity - 1;

		for (;;)
		{
			fc_resarr->lastidx &= fc_mask;
			if (fc_resarr->itemsarr[fc_resarr->lastidx] != fc_resarr->invalidval)
				break;
			fc_resarr->lastidx++;
		}
	}

	*fc_value = fc_resarr->itemsarr[fc_resarr->lastidx];
	return true;
}

/* “便利”的定义是“容易被 ResourceArrayRemove 删除”；我们通过将 lastidx 设置为匹配来帮助这个过程。这避免了在 ResourceOwner 销毁期间删除所有 ResourceArray 项目时的 O(N^2) 成本。 */
static void fc_ResourceArrayFree(ResourceArray *fc_resarr)
{
	if (fc_resarr->itemsarr)
		pfree(fc_resarr->itemsarr);
}


/*****************************************************************************
 *	  EXPORTED ROUTINES														 *
 *****************************************************************************/


/* 如果找到一个元素则返回 true，或如果数组为空则返回 false。 */
ResourceOwner
ResourceOwnerCreate(ResourceOwner fc_parent, const char *fc_name)
{
	ResourceOwner fc_owner;

	fc_owner = (ResourceOwner) MemoryContextAllocZero(TopMemoryContext,
												   sizeof(ResourceOwnerData));
	fc_owner->name = fc_name;

	if (fc_parent)
	{
		fc_owner->parent = fc_parent;
		fc_owner->nextchild = fc_parent->firstchild;
		fc_parent->firstchild = fc_owner;
	}

	fc_ResourceArrayInit(&(fc_owner->bufferarr), BufferGetDatum(InvalidBuffer));
	fc_ResourceArrayInit(&(fc_owner->catrefarr), PointerGetDatum(NULL));
	fc_ResourceArrayInit(&(fc_owner->catlistrefarr), PointerGetDatum(NULL));
	fc_ResourceArrayInit(&(fc_owner->relrefarr), PointerGetDatum(NULL));
	fc_ResourceArrayInit(&(fc_owner->planrefarr), PointerGetDatum(NULL));
	fc_ResourceArrayInit(&(fc_owner->tupdescarr), PointerGetDatum(NULL));
	fc_ResourceArrayInit(&(fc_owner->snapshotarr), PointerGetDatum(NULL));
	fc_ResourceArrayInit(&(fc_owner->filearr), FileGetDatum(-1));
	fc_ResourceArrayInit(&(fc_owner->dsmarr), PointerGetDatum(NULL));
	fc_ResourceArrayInit(&(fc_owner->jitarr), PointerGetDatum(NULL));
	fc_ResourceArrayInit(&(fc_owner->cryptohasharr), PointerGetDatum(NULL));
	fc_ResourceArrayInit(&(fc_owner->hmacarr), PointerGetDatum(NULL));

	return fc_owner;
}

/* 线性数组：只需返回第一个元素。 */
void ResourceOwnerRelease(ResourceOwner fc_owner,
					 ResourceReleasePhase fc_phase,
					 bool fc_isCommit,
					 bool fc_isTopLevel)
{
	/* 哈希：从我们上次所处的位置向前搜索。 */
	fc_ResourceOwnerReleaseInternal(fc_owner, fc_phase, fc_isCommit, fc_isTopLevel);
}

static void fc_ResourceOwnerReleaseInternal(ResourceOwner fc_owner,
							 ResourceReleasePhase fc_phase,
							 bool fc_isCommit,
							 bool fc_isTopLevel)
{
	ResourceOwner fc_child;
	ResourceOwner fc_save;
	ResourceReleaseCallbackItem *fc_item;
	Datum		fc_foundres;

	/* 垃圾处理 ResourceArray（我们不关心其状态） */
	for (fc_child = fc_owner->firstchild; fc_child != NULL; fc_child = fc_child->nextchild)
		fc_ResourceOwnerReleaseInternal(fc_child, fc_phase, fc_isCommit, fc_isTopLevel);

	/* ResourceOwnerCreate */
	fc_save = CurrentResourceOwner;
	CurrentResourceOwner = fc_owner;

	if (fc_phase == RESOURCE_RELEASE_BEFORE_LOCKS)
	{
		/* 创建一个空的 ResourceOwner。 */
		while (fc_ResourceArrayGetAny(&(fc_owner->bufferarr), &fc_foundres))
		{
			Buffer		fc_res = DatumGetBuffer(fc_foundres);

			if (fc_isCommit)
				PrintBufferLeakWarning(fc_res);
			ReleaseBuffer(fc_res);
		}

		/* 同样适用于 relcache 参考 */
		while (fc_ResourceArrayGetAny(&(fc_owner->relrefarr), &fc_foundres))
		{
			Relation	fc_res = (Relation) DatumGetPointer(fc_foundres);

			if (fc_isCommit)
				fc_PrintRelCacheLeakWarning(fc_res);
			RelationClose(fc_res);
		}

		/* 同样适用于动态共享内存段 */
		while (fc_ResourceArrayGetAny(&(fc_owner->dsmarr), &fc_foundres))
		{
			dsm_segment *fc_res = (dsm_segment *) DatumGetPointer(fc_foundres);

			if (fc_isCommit)
				fc_PrintDSMLeakWarning(fc_res);
			dsm_detach(fc_res);
		}

		/* 同样适用于 JIT 上下文 */
		while (fc_ResourceArrayGetAny(&(fc_owner->jitarr), &fc_foundres))
		{
			JitContext *fc_context = (JitContext *) PointerGetDatum(fc_foundres);

			jit_release_context(fc_context);
		}

		/* 同样适用于 cryptohash 上下文 */
		while (fc_ResourceArrayGetAny(&(fc_owner->cryptohasharr), &fc_foundres))
		{
			pg_cryptohash_ctx *fc_context =
			(pg_cryptohash_ctx *) PointerGetDatum(fc_foundres);

			if (fc_isCommit)
				fc_PrintCryptoHashLeakWarning(fc_foundres);
			pg_cryptohash_free(fc_context);
		}

		/* 同样适用于 HMAC 上下文 */
		while (fc_ResourceArrayGetAny(&(fc_owner->hmacarr), &fc_foundres))
		{
			pg_hmac_ctx *fc_context = (pg_hmac_ctx *) PointerGetDatum(fc_foundres);

			if (fc_isCommit)
				fc_PrintHMACLeakWarning(fc_foundres);
			pg_hmac_free(fc_context);
		}
	}
	else if (fc_phase == RESOURCE_RELEASE_LOCKS)
	{
		if (fc_isTopLevel)
		{
			/*
			 * 对于顶级事务，我们将释放所有锁（或至少所有非会话锁），
			 * 所以在递归的顶部只需做一次 lmgr 调用。
			 */
			if (fc_owner == TopTransactionResourceOwner)
			{
				ProcReleaseLocks(fc_isCommit);
				ReleasePredicateLocks(fc_isCommit, false);
			}
		}
		else
		{
			/*
			 * 按零售释放锁。注意，如果我们正在提交一个子事务，
			 * 我们不会立即释放它的锁，而是将它们转移到父级。
			 */
			LOCALLOCK **fc_locks;
			int			fc_nlocks;

			Assert(fc_owner->parent != NULL);

			/*
			 * 将该资源所有者拥有的锁列表传递给锁管理器，除非它已溢出。
			 */
			if (fc_owner->nlocks > MAX_RESOWNER_LOCKS)
			{
				fc_locks = NULL;
				fc_nlocks = 0;
			}
			else
			{
				fc_locks = fc_owner->locks;
				fc_nlocks = fc_owner->nlocks;
			}

			if (fc_isCommit)
				LockReassignCurrentOwner(fc_locks, fc_nlocks);
			else
				LockReleaseCurrentOwner(fc_locks, fc_nlocks);
		}
	}
	else if (fc_phase == RESOURCE_RELEASE_AFTER_LOCKS)
	{
		/*
		 * 释放 catcache 参考。注意，ReleaseCatCache 将从我们的数组中移除
		 * catref 条目，所以我们只需遍历，直到没有为止。
		 *
		 * 与缓冲区钉子一样，提交时如果还有剩余，发出警告。
		 */
		while (fc_ResourceArrayGetAny(&(fc_owner->catrefarr), &fc_foundres))
		{
			HeapTuple	fc_res = (HeapTuple) DatumGetPointer(fc_foundres);

			if (fc_isCommit)
				PrintCatCacheLeakWarning(fc_res);
			ReleaseCatCache(fc_res);
		}

		/* 同样适用于 catcache 列表 */
		while (fc_ResourceArrayGetAny(&(fc_owner->catlistrefarr), &fc_foundres))
		{
			CatCList   *fc_res = (CatCList *) DatumGetPointer(fc_foundres);

			if (fc_isCommit)
				PrintCatCacheListLeakWarning(fc_res);
			ReleaseCatCacheList(fc_res);
		}

		/* 同样适用于 plancache 参考 */
		while (fc_ResourceArrayGetAny(&(fc_owner->planrefarr), &fc_foundres))
		{
			CachedPlan *fc_res = (CachedPlan *) DatumGetPointer(fc_foundres);

			if (fc_isCommit)
				fc_PrintPlanCacheLeakWarning(fc_res);
			ReleaseCachedPlan(fc_res, fc_owner);
		}

		/* 同样适用于 tupdesc 参考 */
		while (fc_ResourceArrayGetAny(&(fc_owner->tupdescarr), &fc_foundres))
		{
			TupleDesc	fc_res = (TupleDesc) DatumGetPointer(fc_foundres);

			if (fc_isCommit)
				fc_PrintTupleDescLeakWarning(fc_res);
			DecrTupleDescRefCount(fc_res);
		}

		/* 同样适用于快照参考 */
		while (fc_ResourceArrayGetAny(&(fc_owner->snapshotarr), &fc_foundres))
		{
			Snapshot	fc_res = (Snapshot) DatumGetPointer(fc_foundres);

			if (fc_isCommit)
				fc_PrintSnapshotLeakWarning(fc_res);
			UnregisterSnapshot(fc_res);
		}

		/* 同样适用于临时文件 */
		while (fc_ResourceArrayGetAny(&(fc_owner->filearr), &fc_foundres))
		{
			File		fc_res = DatumGetFile(fc_foundres);

			if (fc_isCommit)
				fc_PrintFileLeakWarning(fc_res);
			FileClose(fc_res);
		}
	}

	/* 让附加模块也有机会 */
	for (fc_item = ResourceRelease_callbacks; fc_item; fc_item = fc_item->next)
		fc_item->callback(fc_phase, fc_isCommit, fc_isTopLevel, fc_item->arg);

	CurrentResourceOwner = fc_save;
}

/*
 * ResourceOwnerReleaseAllPlanCacheRefs
 *		释放该所有者持有的 plancache 参考（仅）。
 *
 * 我们可能最终会为其他资源类型添加类似功能，
 * 但目前只需要这个。
 */
void ResourceOwnerReleaseAllPlanCacheRefs(ResourceOwner fc_owner)
{
	Datum		fc_foundres;

	while (fc_ResourceArrayGetAny(&(fc_owner->planrefarr), &fc_foundres))
	{
		CachedPlan *fc_res = (CachedPlan *) DatumGetPointer(fc_foundres);

		ReleaseCachedPlan(fc_res, fc_owner);
	}
}

/*
 * ResourceOwnerDelete
 *		删除一个所有者对象及其子孙。
 *
 * 调用者必须已经释放对象树中的所有资源。
 */
void ResourceOwnerDelete(ResourceOwner fc_owner)
{
	/* 我们最好不要删除 CurrentResourceOwner ... */
	Assert(fc_owner != CurrentResourceOwner);

	/* 而且它也最好不拥有任何资源 */
	Assert(fc_owner->bufferarr.nitems == 0);
	Assert(fc_owner->catrefarr.nitems == 0);
	Assert(fc_owner->catlistrefarr.nitems == 0);
	Assert(fc_owner->relrefarr.nitems == 0);
	Assert(fc_owner->planrefarr.nitems == 0);
	Assert(fc_owner->tupdescarr.nitems == 0);
	Assert(fc_owner->snapshotarr.nitems == 0);
	Assert(fc_owner->filearr.nitems == 0);
	Assert(fc_owner->dsmarr.nitems == 0);
	Assert(fc_owner->jitarr.nitems == 0);
	Assert(fc_owner->cryptohasharr.nitems == 0);
	Assert(fc_owner->hmacarr.nitems == 0);
	Assert(fc_owner->nlocks == 0 || fc_owner->nlocks == MAX_RESOWNER_LOCKS + 1);

	/*
	 * 删除子项。递归调用将孩子与我断开链接，
	 * 所以只需在有孩子的情况下进行迭代。
	 */
	while (fc_owner->firstchild != NULL)
		ResourceOwnerDelete(fc_owner->firstchild);

	/*
	 * 我们在删除所有者之前将其与父级断开连接，
	 * 以便在发生错误时不会删除/损坏仍附加到
	 * 所有者树的所有者。漏掉比崩溃更好。
	 */
	ResourceOwnerNewParent(fc_owner, NULL);

	/* 并释放对象。 */
	fc_ResourceArrayFree(&(fc_owner->bufferarr));
	fc_ResourceArrayFree(&(fc_owner->catrefarr));
	fc_ResourceArrayFree(&(fc_owner->catlistrefarr));
	fc_ResourceArrayFree(&(fc_owner->relrefarr));
	fc_ResourceArrayFree(&(fc_owner->planrefarr));
	fc_ResourceArrayFree(&(fc_owner->tupdescarr));
	fc_ResourceArrayFree(&(fc_owner->snapshotarr));
	fc_ResourceArrayFree(&(fc_owner->filearr));
	fc_ResourceArrayFree(&(fc_owner->dsmarr));
	fc_ResourceArrayFree(&(fc_owner->jitarr));
	fc_ResourceArrayFree(&(fc_owner->cryptohasharr));
	fc_ResourceArrayFree(&(fc_owner->hmacarr));

	pfree(fc_owner);
}

/*
 * 获取资源所有者的父级（如果是顶级所有者则返回 NULL）
 */
ResourceOwner
ResourceOwnerGetParent(ResourceOwner fc_owner)
{
	return fc_owner->parent;
}

/*
 * 将资源所有者重新分配为具有新父级
 */
void ResourceOwnerNewParent(ResourceOwner fc_owner,
					   ResourceOwner fc_newparent)
{
	ResourceOwner fc_oldparent = fc_owner->parent;

	if (fc_oldparent)
	{
		if (fc_owner == fc_oldparent->firstchild)
			fc_oldparent->firstchild = fc_owner->nextchild;
		else
		{
			ResourceOwner fc_child;

			for (fc_child = fc_oldparent->firstchild; fc_child; fc_child = fc_child->nextchild)
			{
				if (fc_owner == fc_child->nextchild)
				{
					fc_child->nextchild = fc_owner->nextchild;
					break;
				}
			}
		}
	}

	if (fc_newparent)
	{
		Assert(fc_owner != fc_newparent);
		fc_owner->parent = fc_newparent;
		fc_owner->nextchild = fc_newparent->firstchild;
		fc_newparent->firstchild = fc_owner;
	}
	else
	{
		fc_owner->parent = NULL;
		fc_owner->nextchild = NULL;
	}
}

/*
 * 注册或注销回调函数以进行资源清理
 *
 * 这些函数旨在供动态加载模块使用。
 * 对于内置模块，我们通常只需硬编码适当的调用。
 *
 * 注意，回调发生在提交后或中止后，因此回调
 * 函数只能执行非关键清理。
 */
void RegisterResourceReleaseCallback(ResourceReleaseCallback fc_callback, void *fc_arg)
{
	ResourceReleaseCallbackItem *fc_item;

	fc_item = (ResourceReleaseCallbackItem *)
		MemoryContextAlloc(TopMemoryContext,
						   sizeof(ResourceReleaseCallbackItem));
	fc_item->callback = fc_callback;
	fc_item->arg = fc_arg;
	fc_item->next = ResourceRelease_callbacks;
	ResourceRelease_callbacks = fc_item;
}

void UnregisterResourceReleaseCallback(ResourceReleaseCallback fc_callback, void *fc_arg)
{
	ResourceReleaseCallbackItem *fc_item;
	ResourceReleaseCallbackItem *fc_prev;

	fc_prev = NULL;
	for (fc_item = ResourceRelease_callbacks; fc_item; fc_prev = fc_item, fc_item = fc_item->next)
	{
		if (fc_item->callback == fc_callback && fc_item->arg == fc_arg)
		{
			if (fc_prev)
				fc_prev->next = fc_item->next;
			else
				ResourceRelease_callbacks = fc_item->next;
			pfree(fc_item);
			break;
		}
	}
}

/*
 * 为当前进程建立 AuxProcessResourceOwner。
 */
void CreateAuxProcessResourceOwner(void)
{
	Assert(AuxProcessResourceOwner == NULL);
	Assert(CurrentResourceOwner == NULL);
	AuxProcessResourceOwner = ResourceOwnerCreate(NULL, "AuxiliaryProcess");
	CurrentResourceOwner = AuxProcessResourceOwner;

	/*
	 * 注册 shmem-exit 回调以清理 aux-process 资源
	 * 所有者。（这需要在例如 ShutdownXLOG 之后运行。）
	 */
	on_shmem_exit(fc_ReleaseAuxProcessResourcesCallback, 0);
}

/*
 * 便利例程以释放在 AuxProcessResourceOwner 中跟踪的所有资源
 * （但该 resowner 在这里不被销毁）。
 * 如果 isCommit 为真，则警告泄漏的资源。
 */
void ReleaseAuxProcessResources(bool fc_isCommit)
{
	/*
	 * 在写作时，唯一可以实际释放的只是
	 * 缓冲区钉子；但我们最好还是执行完整的释放协议。
	 */
	ResourceOwnerRelease(AuxProcessResourceOwner,
						 RESOURCE_RELEASE_BEFORE_LOCKS,
						 fc_isCommit, true);
	ResourceOwnerRelease(AuxProcessResourceOwner,
						 RESOURCE_RELEASE_LOCKS,
						 fc_isCommit, true);
	ResourceOwnerRelease(AuxProcessResourceOwner,
						 RESOURCE_RELEASE_AFTER_LOCKS,
						 fc_isCommit, true);
}

/*
 * 同样的 shmem-exit 回调。
 * 如果进程退出代码为零（即正常），则警告泄漏的资源。
 */
static void fc_ReleaseAuxProcessResourcesCallback(int fc_code, Datum fc_arg)
{
	bool		fc_isCommit = (fc_code == 0);

	ReleaseAuxProcessResources(fc_isCommit);
}


/*
 * 确保ResourceOwner的缓存数组至少有足够的空间容纳一个新的条目。
 *
 * 这与实际插入条目是分开的，因为如果内存耗尽，必须在获取资源*之前*执行此操作。
 */
void ResourceOwnerEnlargeBuffers(ResourceOwner fc_owner)
{
	/* 我们之前允许在没有resowner的情况下锁定缓存，但现在不再允许 */
	Assert(fc_owner != NULL);
	fc_ResourceArrayEnlarge(&(fc_owner->bufferarr));
}

/*
 * 记住，缓存锁是由ResourceOwner拥有的
 *
 * 调用者必须先调用ResourceOwnerEnlargeBuffers()
 */
void ResourceOwnerRememberBuffer(ResourceOwner fc_owner, Buffer fc_buffer)
{
	fc_ResourceArrayAdd(&(fc_owner->bufferarr), BufferGetDatum(fc_buffer));
}

/*
 * 忘记缓存锁是由ResourceOwner拥有的
 */
void ResourceOwnerForgetBuffer(ResourceOwner fc_owner, Buffer fc_buffer)
{
	if (!fc_ResourceArrayRemove(&(fc_owner->bufferarr), BufferGetDatum(fc_buffer)))
		elog(ERROR, "buffer %d is not owned by resource owner %s",
			 fc_buffer, fc_owner->name);
}

/*
 * 记住，局部锁是由ResourceOwner拥有的
 *
 * 这与其他Remember函数不同，因为锁的列表仅是一个有损缓存。它最多可以保存MAX_RESOWNER_LOCKS个条目，当溢出时，我们将停止跟踪锁。只记住最多MAX_RESOWNER_LOCKS个条目的目的在于，如果持有大量锁，ResourceOwnerForgetLock就不需要扫描一个大的数组来查找条目。
 */
void ResourceOwnerRememberLock(ResourceOwner fc_owner, LOCALLOCK *fc_locallock)
{
	Assert(fc_locallock != NULL);

	if (fc_owner->nlocks > MAX_RESOWNER_LOCKS)
		return;					/* 我们已经溢出 */

	if (fc_owner->nlocks < MAX_RESOWNER_LOCKS)
		fc_owner->locks[fc_owner->nlocks] = fc_locallock;
	else
	{
		/* 溢出 */
	}
	fc_owner->nlocks++;
}

/*
 * 忘记局部锁是由ResourceOwner拥有的
 */
void ResourceOwnerForgetLock(ResourceOwner fc_owner, LOCALLOCK *fc_locallock)
{
	int			fc_i;

	if (fc_owner->nlocks > MAX_RESOWNER_LOCKS)
		return;					/* 我们已经溢出 */

	Assert(fc_owner->nlocks > 0);
	for (fc_i = fc_owner->nlocks - 1; fc_i >= 0; fc_i--)
	{
		if (fc_locallock == fc_owner->locks[fc_i])
		{
			fc_owner->locks[fc_i] = fc_owner->locks[fc_owner->nlocks - 1];
			fc_owner->nlocks--;
			return;
		}
	}
	elog(ERROR, "lock reference %p is not owned by resource owner %s",
		 fc_locallock, fc_owner->name);
}

/*
 * 确保ResourceOwner的catcache引用数组至少有足够的空间容纳一个新的条目。
 *
 * 这与实际插入条目是分开的，因为如果内存耗尽，必须在获取资源*之前*执行此操作。
 */
void ResourceOwnerEnlargeCatCacheRefs(ResourceOwner fc_owner)
{
	fc_ResourceArrayEnlarge(&(fc_owner->catrefarr));
}

/*
 * 记住，catcache引用是由ResourceOwner拥有的
 *
 * 调用者必须先调用ResourceOwnerEnlargeCatCacheRefs()
 */
void ResourceOwnerRememberCatCacheRef(ResourceOwner fc_owner, HeapTuple fc_tuple)
{
	fc_ResourceArrayAdd(&(fc_owner->catrefarr), PointerGetDatum(fc_tuple));
}

/*
 * 忘记catcache引用是由ResourceOwner拥有的
 */
void ResourceOwnerForgetCatCacheRef(ResourceOwner fc_owner, HeapTuple fc_tuple)
{
	if (!fc_ResourceArrayRemove(&(fc_owner->catrefarr), PointerGetDatum(fc_tuple)))
		elog(ERROR, "catcache reference %p is not owned by resource owner %s",
			 fc_tuple, fc_owner->name);
}

/*
 * 确保ResourceOwner的catcache-list引用数组至少有足够的空间容纳一个新的条目。
 *
 * 这与实际插入条目是分开的，因为如果内存耗尽，必须在获取资源*之前*执行此操作。
 */
void ResourceOwnerEnlargeCatCacheListRefs(ResourceOwner fc_owner)
{
	fc_ResourceArrayEnlarge(&(fc_owner->catlistrefarr));
}

/*
 * 记住，catcache-list引用是由ResourceOwner拥有的
 *
 * 调用者必须先调用ResourceOwnerEnlargeCatCacheListRefs()
 */
void ResourceOwnerRememberCatCacheListRef(ResourceOwner fc_owner, CatCList *fc_list)
{
	fc_ResourceArrayAdd(&(fc_owner->catlistrefarr), PointerGetDatum(fc_list));
}

/*
 * 忘记catcache-list引用是由ResourceOwner拥有的
 */
void ResourceOwnerForgetCatCacheListRef(ResourceOwner fc_owner, CatCList *fc_list)
{
	if (!fc_ResourceArrayRemove(&(fc_owner->catlistrefarr), PointerGetDatum(fc_list)))
		elog(ERROR, "catcache list reference %p is not owned by resource owner %s",
			 fc_list, fc_owner->name);
}

/*
 * 确保ResourceOwner的relcache引用数组至少有足够的空间容纳一个新的条目。
 *
 * 这与实际插入条目是分开的，因为如果内存耗尽，必须在获取资源*之前*执行此操作。
 */
void ResourceOwnerEnlargeRelationRefs(ResourceOwner fc_owner)
{
	fc_ResourceArrayEnlarge(&(fc_owner->relrefarr));
}

/*
 * 记住，relcache引用是由ResourceOwner拥有的
 *
 * 调用者必须先调用ResourceOwnerEnlargeRelationRefs()
 */
void ResourceOwnerRememberRelationRef(ResourceOwner fc_owner, Relation fc_rel)
{
	fc_ResourceArrayAdd(&(fc_owner->relrefarr), PointerGetDatum(fc_rel));
}

/*
 * 忘记relcache引用是由ResourceOwner拥有的
 */
void ResourceOwnerForgetRelationRef(ResourceOwner fc_owner, Relation fc_rel)
{
	if (!fc_ResourceArrayRemove(&(fc_owner->relrefarr), PointerGetDatum(fc_rel)))
		elog(ERROR, "relcache reference %s is not owned by resource owner %s",
			 RelationGetRelationName(fc_rel), fc_owner->name);
}

/*
 * 调试子例程
 */
static void fc_PrintRelCacheLeakWarning(Relation fc_rel)
{
	elog(WARNING, "relcache reference leak: relation \"%s\" not closed",
		 RelationGetRelationName(fc_rel));
}

/*
 * 确保ResourceOwner的plancache引用数组至少有足够的空间容纳一个新的条目。
 *
 * 这与实际插入条目是分开的，因为如果内存耗尽，必须在获取资源*之前*执行此操作。
 */
void ResourceOwnerEnlargePlanCacheRefs(ResourceOwner fc_owner)
{
	fc_ResourceArrayEnlarge(&(fc_owner->planrefarr));
}

/*
 * 记住，plancache引用是由ResourceOwner拥有的
 *
 * 调用者必须先调用ResourceOwnerEnlargePlanCacheRefs()
 */
void ResourceOwnerRememberPlanCacheRef(ResourceOwner fc_owner, CachedPlan *fc_plan)
{
	fc_ResourceArrayAdd(&(fc_owner->planrefarr), PointerGetDatum(fc_plan));
}

/*
 * 忘记plancache引用是由ResourceOwner拥有的
 */
void ResourceOwnerForgetPlanCacheRef(ResourceOwner fc_owner, CachedPlan *fc_plan)
{
	if (!fc_ResourceArrayRemove(&(fc_owner->planrefarr), PointerGetDatum(fc_plan)))
		elog(ERROR, "plancache reference %p is not owned by resource owner %s",
			 fc_plan, fc_owner->name);
}

/*
 * 调试子例程
 */
static void fc_PrintPlanCacheLeakWarning(CachedPlan *fc_plan)
{
	elog(WARNING, "plancache reference leak: plan %p not closed", fc_plan);
}

/*
 * 确保ResourceOwner的tupdesc引用数组至少有足够的空间容纳一个新的条目。
 *
 * 这与实际插入条目是分开的，因为如果内存耗尽，必须在获取资源*之前*执行此操作。
 */
void ResourceOwnerEnlargeTupleDescs(ResourceOwner fc_owner)
{
	fc_ResourceArrayEnlarge(&(fc_owner->tupdescarr));
}

/*
 * 记住，tupdesc引用是由ResourceOwner拥有的
 *
 * 调用者必须先调用ResourceOwnerEnlargeTupleDescs()
 */
void ResourceOwnerRememberTupleDesc(ResourceOwner fc_owner, TupleDesc fc_tupdesc)
{
	fc_ResourceArrayAdd(&(fc_owner->tupdescarr), PointerGetDatum(fc_tupdesc));
}

/*
 * 忘记tupdesc引用是由ResourceOwner拥有的
 */
void ResourceOwnerForgetTupleDesc(ResourceOwner fc_owner, TupleDesc fc_tupdesc)
{
	if (!fc_ResourceArrayRemove(&(fc_owner->tupdescarr), PointerGetDatum(fc_tupdesc)))
		elog(ERROR, "tupdesc reference %p is not owned by resource owner %s",
			 fc_tupdesc, fc_owner->name);
}

/*
 * 调试子例程
 */
static void fc_PrintTupleDescLeakWarning(TupleDesc fc_tupdesc)
{
	elog(WARNING,
		 "TupleDesc reference leak: TupleDesc %p (%u,%d) still referenced",
		 fc_tupdesc, fc_tupdesc->tdtypeid, fc_tupdesc->tdtypmod);
}

/*
 * 确保ResourceOwner的snapshot引用数组至少有足够的空间容纳一个新的条目。
 *
 * 这与实际插入条目是分开的，因为如果内存耗尽，必须在获取资源*之前*执行此操作。
 */
void ResourceOwnerEnlargeSnapshots(ResourceOwner fc_owner)
{
	fc_ResourceArrayEnlarge(&(fc_owner->snapshotarr));
}

/*
 * 记住，snapshot引用是由ResourceOwner拥有的
 *
 * 调用者必须先调用ResourceOwnerEnlargeSnapshots()
 */
void ResourceOwnerRememberSnapshot(ResourceOwner fc_owner, Snapshot fc_snapshot)
{
	fc_ResourceArrayAdd(&(fc_owner->snapshotarr), PointerGetDatum(fc_snapshot));
}

/*
 * 忘记snapshot引用是由ResourceOwner拥有的
 */
void ResourceOwnerForgetSnapshot(ResourceOwner fc_owner, Snapshot fc_snapshot)
{
	if (!fc_ResourceArrayRemove(&(fc_owner->snapshotarr), PointerGetDatum(fc_snapshot)))
		elog(ERROR, "snapshot reference %p is not owned by resource owner %s",
			 fc_snapshot, fc_owner->name);
}

/*
 * 调试子例程
 */
static void fc_PrintSnapshotLeakWarning(Snapshot fc_snapshot)
{
	elog(WARNING, "Snapshot reference leak: Snapshot %p still referenced",
		 fc_snapshot);
}


/*
 * 确保ResourceOwner的files引用数组至少有足够的空间容纳一个新的条目。
 *
 * 这与实际插入条目是分开的，因为如果内存耗尽，必须在获取资源*之前*执行此操作。
 */
void ResourceOwnerEnlargeFiles(ResourceOwner fc_owner)
{
	fc_ResourceArrayEnlarge(&(fc_owner->filearr));
}

/*
 * 请记住，一个临时文件由资源拥有者拥有
 *
 * 调用者必须先执行 ResourceOwnerEnlargeFiles()
 */
void ResourceOwnerRememberFile(ResourceOwner fc_owner, File fc_file)
{
	fc_ResourceArrayAdd(&(fc_owner->filearr), FileGetDatum(fc_file));
}


/*
 * 忘记一个临时文件由 ResourceOwner 所拥有
 */
void ResourceOwnerForgetFile(ResourceOwner fc_owner, File fc_file)
{
	if (!fc_ResourceArrayRemove(&(fc_owner->filearr), FileGetDatum(fc_file)))
		elog(ERROR, "temporary file %d is not owned by resource owner %s",
			 fc_file, fc_owner->name);
}

/*
 * 调试子例程
 */
static void fc_PrintFileLeakWarning(File fc_file)
{
	elog(WARNING, "temporary file leak: File %d still referenced",
		 fc_file);
}

/*
 * 确保在 ResourceOwner 的动态共享内存段引用数组中至少有一个额外条目的空间。
 *
 * 这与实际插入条目是分开的，因为如果我们内存不足，必须在获取资源之前这样做是至关重要的。
 */
void ResourceOwnerEnlargeDSMs(ResourceOwner fc_owner)
{
	fc_ResourceArrayEnlarge(&(fc_owner->dsmarr));
}

/*
 * 记住动态共享内存段由 ResourceOwner 所拥有
 *
 * 调用者必须先调用 ResourceOwnerEnlargeDSMs()
 */
void ResourceOwnerRememberDSM(ResourceOwner fc_owner, dsm_segment *fc_seg)
{
	fc_ResourceArrayAdd(&(fc_owner->dsmarr), PointerGetDatum(fc_seg));
}

/*
 * 忘记动态共享内存段由 ResourceOwner 所拥有
 */
void ResourceOwnerForgetDSM(ResourceOwner fc_owner, dsm_segment *fc_seg)
{
	if (!fc_ResourceArrayRemove(&(fc_owner->dsmarr), PointerGetDatum(fc_seg)))
		elog(ERROR, "dynamic shared memory segment %u is not owned by resource owner %s",
			 dsm_segment_handle(fc_seg), fc_owner->name);
}

/*
 * 调试子例程
 */
static void fc_PrintDSMLeakWarning(dsm_segment *fc_seg)
{
	elog(WARNING, "dynamic shared memory leak: segment %u still referenced",
		 dsm_segment_handle(fc_seg));
}

/*
 * 确保在 ResourceOwner 的 JIT 上下文引用数组中至少有一个额外条目的空间。
 *
 * 这与实际插入条目是分开的，因为如果我们内存不足，必须在获取资源之前这样做是至关重要的。
 */
void ResourceOwnerEnlargeJIT(ResourceOwner fc_owner)
{
	fc_ResourceArrayEnlarge(&(fc_owner->jitarr));
}

/*
 * 记住 JIT 上下文由 ResourceOwner 所拥有
 *
 * 调用者必须先调用 ResourceOwnerEnlargeJIT()
 */
void ResourceOwnerRememberJIT(ResourceOwner fc_owner, Datum fc_handle)
{
	fc_ResourceArrayAdd(&(fc_owner->jitarr), fc_handle);
}

/*
 * 忘记 JIT 上下文由 ResourceOwner 所拥有
 */
void ResourceOwnerForgetJIT(ResourceOwner fc_owner, Datum fc_handle)
{
	if (!fc_ResourceArrayRemove(&(fc_owner->jitarr), fc_handle))
		elog(ERROR, "JIT context %p is not owned by resource owner %s",
			 DatumGetPointer(fc_handle), fc_owner->name);
}

/*
 * 确保在 ResourceOwner 的 cryptohash 上下文引用数组中至少有一个额外条目的空间。
 *
 * 这与实际插入条目是分开的，因为如果我们内存不足，必须在获取资源之前这样做是至关重要的。
 */
void ResourceOwnerEnlargeCryptoHash(ResourceOwner fc_owner)
{
	fc_ResourceArrayEnlarge(&(fc_owner->cryptohasharr));
}

/*
 * 记住 cryptohash 上下文由 ResourceOwner 所拥有
 *
 * 调用者必须先调用 ResourceOwnerEnlargeCryptoHash()
 */
void ResourceOwnerRememberCryptoHash(ResourceOwner fc_owner, Datum fc_handle)
{
	fc_ResourceArrayAdd(&(fc_owner->cryptohasharr), fc_handle);
}

/*
 * 忘记 cryptohash 上下文由 ResourceOwner 所拥有
 */
void ResourceOwnerForgetCryptoHash(ResourceOwner fc_owner, Datum fc_handle)
{
	if (!fc_ResourceArrayRemove(&(fc_owner->cryptohasharr), fc_handle))
		elog(ERROR, "cryptohash context %p is not owned by resource owner %s",
			 DatumGetPointer(fc_handle), fc_owner->name);
}

/*
 * 调试子例程
 */
static void fc_PrintCryptoHashLeakWarning(Datum fc_handle)
{
	elog(WARNING, "cryptohash context reference leak: context %p still referenced",
		 DatumGetPointer(fc_handle));
}

/*
 * 确保在 ResourceOwner 的 hmac 上下文引用数组中至少有一个额外条目的空间。
 *
 * 这与实际插入条目是分开的，因为如果我们内存不足，必须在获取资源之前这样做是至关重要的。
 */
void ResourceOwnerEnlargeHMAC(ResourceOwner fc_owner)
{
	fc_ResourceArrayEnlarge(&(fc_owner->hmacarr));
}

/*
 * 记住 HMAC 上下文由 ResourceOwner 所拥有
 *
 * 调用者必须先调用 ResourceOwnerEnlargeHMAC()
 */
void ResourceOwnerRememberHMAC(ResourceOwner fc_owner, Datum fc_handle)
{
	fc_ResourceArrayAdd(&(fc_owner->hmacarr), fc_handle);
}

/*
 * 忘记 HMAC 上下文由 ResourceOwner 所拥有
 */
void ResourceOwnerForgetHMAC(ResourceOwner fc_owner, Datum fc_handle)
{
	if (!fc_ResourceArrayRemove(&(fc_owner->hmacarr), fc_handle))
		elog(ERROR, "HMAC context %p is not owned by resource owner %s",
			 DatumGetPointer(fc_handle), fc_owner->name);
}

/*
 * 调试子例程
 */
static void fc_PrintHMACLeakWarning(Datum fc_handle)
{
	elog(WARNING, "HMAC context reference leak: context %p still referenced",
		 DatumGetPointer(fc_handle));
}
